local lapis = require "lapis"
local utils = require "kong.tools.utils"
local singletons = require "kong.singletons"
local api_helpers = require "kong.api.api_helpers"
local Endpoints = require "kong.api.endpoints"


local ngx = ngx
local type = type
local pairs = pairs
local ipairs = ipairs


local app = lapis.Application()


app.default_route = api_helpers.default_route
app.handle_404 = api_helpers.handle_404
app.handle_error = api_helpers.handle_error
app:before_filter(api_helpers.before_filter)


ngx.log(ngx.DEBUG, "Loading Admin API endpoints")


-- Load core routes
for _, v in ipairs({ "kong", "health", "cache", "config", "clustering" }) do
    local routes = require("kong.api.routes." .. v)
    api_helpers.attach_routes(app, routes)
end


do
    -- This function takes the auto-generated routes and then customizes them
    -- based on custom_endpoints. It will add one argument to actual function
    -- call `parent` that the customized function can use to call the original
    -- auto-generated function.
    --
    -- E.g. the `/routes/:routes` API gets autogenerated from `routes` DAO.
    -- Now if your plugin adds `api.lua` that also defines the same endpoint:
    -- `/routes/:routes`, it means that the plugin one overrides the original
    -- function. Original is kept and passed to the customized function as an
    -- function argument (of course usually plugins want to only customize
    -- the autogenerated endpoints the plugin's own DAOs introduced).
    local function customize_routes(routes, custom_endpoints, schema)
        for route_pattern, verbs in pairs(custom_endpoints) do
            if type(verbs) == "table" then
                local methods = verbs.methods or verbs

                if routes[route_pattern] == nil then
                    routes[route_pattern] = {
                        schema = verbs.schema or schema,
                        methods = methods
                    }

                else
                    for method, handler in pairs(methods) do
                        local parent = routes[route_pattern]["methods"][method]
                        if parent ~= nil and type(handler) == "function" then
                            routes[route_pattern]["methods"][method] = function(self, db, helpers)
                                return handler(self, db, helpers, function(post_process)
                                    return parent(self, db, helpers, post_process)
                                end)
                            end

                        else
                            routes[route_pattern]["methods"][method] = handler
                        end
                    end
                end
            end
        end
    end

    local function is_new_db_routes(routes)
        for _, verbs in pairs(routes) do
            if type(verbs) == "table" then -- ignore "before" functions
                return verbs.schema
            end
        end
    end

    local routes = {}

    -- DAO Routes
    for _, dao in pairs(singletons.db.daos) do
        if dao.schema.generate_admin_api ~= false and not dao.schema.legacy then
            routes = Endpoints.new(dao.schema, routes)
        end
    end

    -- Custom Routes
    for _, dao in pairs(singletons.db.daos) do
        local schema = dao.schema
        local ok, custom_endpoints = utils.load_module_if_exists("kong.api.routes." .. schema.name)
        if ok then
            customize_routes(routes, custom_endpoints, schema)
        end
    end

    -- Plugin Routes
    if singletons.configuration and singletons.configuration.loaded_plugins then
        for k in pairs(singletons.configuration.loaded_plugins) do
            local loaded, custom_endpoints = utils.load_module_if_exists("kong.plugins." .. k .. ".api")
            if loaded then
                ngx.log(ngx.DEBUG, "Loading API endpoints for plugin: ", k)
                if is_new_db_routes(custom_endpoints) then
                    customize_routes(routes, custom_endpoints, custom_endpoints.schema)

                else
                    api_helpers.attach_routes(app, custom_endpoints)
                end

            else
                ngx.log(ngx.DEBUG, "No API endpoints loaded for plugin: ", k)
            end
        end
    end

    api_helpers.attach_new_db_routes(app, routes)
end

return app
